package org.eiahe.hr.attendance.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eiahe.hr.attendance.domain.AnnualLeave;
import org.eiahe.hr.attendance.domain.AnnualLeaveAdjustLog;
import org.eiahe.hr.attendance.domain.AnnualLeaveRule;
import org.eiahe.hr.attendance.domain.bo.AnnualLeaveAdjustBo;
import org.eiahe.hr.attendance.domain.bo.AnnualLeaveBo;
import org.eiahe.hr.attendance.domain.vo.AnnualLeaveAdjustLogVo;
import org.eiahe.hr.attendance.domain.vo.AnnualLeaveVo;
import org.eiahe.hr.attendance.mapper.AnnualLeaveAdjustLogMapper;
import org.eiahe.hr.attendance.mapper.AnnualLeaveMapper;
import org.eiahe.hr.attendance.mapper.AnnualLeaveRuleMapper;
import org.eiahe.hr.attendance.service.IAnnualLeaveService;
import org.eiahe.hr.attendance.util.WorkYearsUtil;
import org.ehe.common.core.exception.ServiceException;
import org.ehe.common.mybatis.core.page.PageQuery;
import org.ehe.common.mybatis.core.page.TableDataInfo;
import org.ehe.common.satoken.utils.LoginHelper;
import org.ehe.system.domain.HrEmployee;
import org.ehe.system.mapper.HrEmployeeMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 年假核算Service业务层处理
 *
 * @author zhangnn
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class AnnualLeaveServiceImpl implements IAnnualLeaveService {

    private final AnnualLeaveMapper baseMapper;
    private final AnnualLeaveAdjustLogMapper adjustLogMapper;
    private final AnnualLeaveRuleMapper annualLeaveRuleMapper;
    private final HrEmployeeMapper employeeMapper;
    private final Converter converter;

    @Override
    public AnnualLeaveVo queryById(Long id) {
        AnnualLeaveVo vo = baseMapper.selectVoById(id);
        if (vo != null) {
            enrichVo(vo);
        }
        return vo;
    }

    @Override
    public TableDataInfo<AnnualLeaveVo> queryPageList(AnnualLeaveBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<AnnualLeave> lqw = buildQueryWrapper(bo);
        Page<AnnualLeaveVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        result.getRecords().forEach(this::enrichVo);
        return TableDataInfo.build(result);
    }

    @Override
    public List<AnnualLeaveVo> queryList(AnnualLeaveBo bo) {
        LambdaQueryWrapper<AnnualLeave> lqw = buildQueryWrapper(bo);
        List<AnnualLeaveVo> list = baseMapper.selectVoList(lqw);
        list.forEach(this::enrichVo);
        return list;
    }

    private LambdaQueryWrapper<AnnualLeave> buildQueryWrapper(AnnualLeaveBo bo) {
        LambdaQueryWrapper<AnnualLeave> lqw = Wrappers.lambdaQuery();
        List<String> empNos = new ArrayList<>();
        if (StringUtils.isNotBlank(bo.getEmployeeName())) {
            List<HrEmployee> hrEmployees = employeeMapper.selectList(
                new LambdaQueryWrapper<HrEmployee>().like(HrEmployee::getEmployeeName, bo.getEmployeeName())
            );
            empNos = Optional.ofNullable(hrEmployees)
                .orElse(Collections.emptyList())
                .stream()
                .map(HrEmployee::getEmployeeNo)
                .collect(Collectors.toList());

            if (CollUtil.isEmpty(empNos)) {
                lqw.eq(AnnualLeave::getEmployeeNo, -1);
            } else {
                lqw.in(AnnualLeave::getEmployeeNo, empNos);
            }
        }

        lqw.eq(bo.getUserId() != null, AnnualLeave::getUserId, bo.getUserId());
        lqw.eq(bo.getYear() != null, AnnualLeave::getYear, bo.getYear());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), AnnualLeave::getStatus, bo.getStatus());
        lqw.orderByDesc(AnnualLeave::getYear).orderByAsc(AnnualLeave::getEmployeeNo);
        return lqw;
    }

    /**
     * 丰富VO数据
     */
    private void enrichVo(AnnualLeaveVo vo) {
        // 填充员工姓名
        if (StringUtils.isNotBlank(vo.getEmployeeNo())) {
            HrEmployee employee = employeeMapper.selectOne(
                    Wrappers.lambdaQuery(HrEmployee.class)
                            .eq(HrEmployee::getEmployeeNo, vo.getEmployeeNo())
                            .last("LIMIT 1"));
            if (employee != null) {
                vo.setEmployeeName(employee.getEmployeeName());
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean insertByBo(AnnualLeaveBo bo) {
        AnnualLeave add = converter.convert(bo, AnnualLeave.class);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByBo(AnnualLeaveBo bo) {
        AnnualLeave update = converter.convert(bo, AnnualLeave.class);
        return baseMapper.updateById(update) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            // 可以添加删除校验逻辑
        }
        return baseMapper.deleteByIds(ids) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void calculateAnnualLeaveForYear(Integer year) {
        log.info("开始执行年度年假核算，年份：{}", year);

        // 查询所有在职员工
        List<HrEmployee> employees = employeeMapper.selectList(
                Wrappers.lambdaQuery(HrEmployee.class)
                        .in(HrEmployee::getEmployeeStatus, "1", "2")); // 1在职 2试用

        log.info("查询到在职员工数量：{}", employees.size());

        int successCount = 0;
        int failCount = 0;

        for (HrEmployee employee : employees) {
            try {
                calculateAnnualLeaveForEmployee(employee.getEmployeeNo(), year);
                successCount++;
            } catch (Exception e) {
                failCount++;
                log.error("员工{}年假核算失败", employee.getEmployeeNo(), e);
            }
        }

        log.info("年度年假核算完成，成功：{}，失败：{}", successCount, failCount);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void calculateAnnualLeaveForEmployee(String employeeNo, Integer year) {
        log.info("开始计算员工{}的{}年年假", employeeNo, year);

        // 1. 查询员工信息
        HrEmployee employee = employeeMapper.selectOne(
                Wrappers.lambdaQuery(HrEmployee.class)
                        .eq(HrEmployee::getEmployeeNo, employeeNo)
                        .last("LIMIT 1"));

        if (employee == null) {
            throw new ServiceException("员工不存在：" + employeeNo);
        }

        if (employee.getEntryDate() == null) {
            throw new ServiceException("员工入职日期为空：" + employeeNo);
        }

        // 2. 计算工龄（以年份的1月1日为准）
        LocalDate calculateDate = LocalDate.of(year, 1, 1);
        LocalDate entryDate = convertToLocalDate(employee.getEntryDate());

        // 如果入职日期在核算日期之后，不进行核算
        if (entryDate.isAfter(calculateDate)) {
            log.warn("员工{}入职日期{}在核算日期{}之后，跳过核算",
                    employeeNo, entryDate, calculateDate);
            return;
        }

        int workYears = WorkYearsUtil.calculateWorkYears(entryDate, calculateDate);
        long workMonths = WorkYearsUtil.calculateWorkMonths(entryDate, calculateDate);

        log.info("员工{}入职日期：{}，核算日期：{}，工龄：{}年{}个月",
                employeeNo, entryDate, calculateDate, workYears, workMonths);

        // 3. 根据工龄查询年假规则
        AnnualLeaveRule rule = findMatchingRule(workYears);
        if (rule == null) {
            log.warn("员工{}工龄{}年，未找到匹配的年假规则", employeeNo, workYears);
            return;
        }

        log.info("员工{}匹配到年假规则：{}，基本年假天数：{}",
                employeeNo, rule.getRuleName(), rule.getLeaveDays());

        // 4. 按月比例计算实际年假天数
        BigDecimal actualLeaveDays = calculateActualLeaveDays(entryDate, year, rule.getLeaveDays());

        log.info("员工{}工作月数：{}，按比例计算后年假天数：{}",
                employeeNo, workMonths, actualLeaveDays);

        // 5. 查询是否已存在该年份的年假记录
        AnnualLeave existingLeave = baseMapper.selectOne(
                Wrappers.lambdaQuery(AnnualLeave.class)
                        .eq(AnnualLeave::getEmployeeNo, employeeNo)
                        .eq(AnnualLeave::getYear, year)
                        .last("LIMIT 1"));

        if (existingLeave != null) {
            // 更新已有记录
            existingLeave.setWorkYears(workYears);
            existingLeave.setTotalDays(actualLeaveDays.intValue());
            // 重新计算剩余天数 = 总天数 - 已用天数
            existingLeave.setRemainingDays(actualLeaveDays.subtract(existingLeave.getUsedDays()));
            baseMapper.updateById(existingLeave);
            log.info("更新员工{}的{}年年假记录，总天数：{}，已用：{}，剩余：{}",
                    employeeNo, year, existingLeave.getTotalDays(),
                    existingLeave.getUsedDays(), existingLeave.getRemainingDays());
        } else {
            // 创建新记录
            AnnualLeave newLeave = new AnnualLeave();
            newLeave.setEmployeeNo(employeeNo);
            newLeave.setUserId(employee.getUserId());
            newLeave.setYear(year);
            newLeave.setWorkYears(workYears);
            newLeave.setTotalDays(actualLeaveDays.intValue());
            newLeave.setUsedDays(BigDecimal.ZERO);
            newLeave.setRemainingDays(actualLeaveDays);
            newLeave.setCreateTime(new Date());
            newLeave.setStatus("0");
            baseMapper.insert(newLeave);
            log.info("创建员工{}的{}年年假记录，总天数：{}",
                    employeeNo, year, newLeave.getTotalDays());
        }
    }

    /**
     * 根据工龄查找匹配的年假规则
     */
    private AnnualLeaveRule findMatchingRule(int workYears) {
        // 查询所有生效的年假规则
        List<AnnualLeaveRule> rules = annualLeaveRuleMapper.selectList(
                Wrappers.lambdaQuery(AnnualLeaveRule.class)
                        .eq(AnnualLeaveRule::getIsActive, "1")
                        .eq(AnnualLeaveRule::getStatus, "0")
                        .orderByAsc(AnnualLeaveRule::getMinWorkYears));

        for (AnnualLeaveRule rule : rules) {
            if (workYears >= rule.getMinWorkYears()) {
                if (rule.getMaxWorkYears() == null || workYears <= rule.getMaxWorkYears()) {
                    return rule;
                }
            }
        }

        return null;
    }

    /**
     * 按月比例计算实际年假天数
     * 根据年假规则表和实际工作月数计算
     * 规则：工作不足12个月不给年假
     *
     * @param entryDate     入职日期
     * @param year          核算年份
     * @param baseLeaveDays 基本年假天数(来自年假规则表)
     * @return 实际年假天数
     */
    private BigDecimal calculateActualLeaveDays(LocalDate entryDate, Integer year, Integer baseLeaveDays) {
        // 计算到年份1月1日的工作月数
        LocalDate calculateDate = LocalDate.of(year, 1, 1);
        long totalMonths = WorkYearsUtil.calculateWorkMonths(entryDate, calculateDate);

        // 工作不足12个月(不足1年)，不给年假
        if (totalMonths < 12) {
            log.debug("员工工作月数：{}，不足12个月，不给年假", totalMonths);
            return BigDecimal.ZERO;
        }

        // 工作满12个月及以上，返回年假规则表中配置的天数
        log.debug("员工工作月数：{}，规则天数：{}，实际年假：{}天",
                totalMonths, baseLeaveDays, baseLeaveDays);

        return BigDecimal.valueOf(baseLeaveDays);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean adjustAnnualLeave(AnnualLeaveAdjustBo adjustBo) {
        // 1. 查询年假记录
        Long id = adjustBo.getId();
        BigDecimal adjustDays = adjustBo.getAdjustDays();
        String adjustType = adjustBo.getAdjustType();
        String adjustReason = adjustBo.getAdjustReason();
        AnnualLeave annualLeave = baseMapper.selectById(id);
        if (annualLeave == null) {
            throw new ServiceException("年假记录不存在");
        }

        BigDecimal beforeDays = annualLeave.getRemainingDays();
        BigDecimal afterDays;

        // 2. 根据调整类型计算调整后天数
        if ("ADD".equals(adjustType)) {
            afterDays = beforeDays.add(adjustDays);
        } else if ("REDUCE".equals(adjustType)) {
            afterDays = beforeDays.subtract(adjustDays);
            if (afterDays.compareTo(BigDecimal.ZERO) < 0) {
                throw new ServiceException("剩余天数不足，无法减少");
            }
        } else {
            throw new ServiceException("调整类型错误");
        }

        // 3. 更新年假记录
        annualLeave.setRemainingDays(afterDays);
        baseMapper.updateById(annualLeave);

        // 4. 记录调整日志
        AnnualLeaveAdjustLog log = new AnnualLeaveAdjustLog();
        log.setAnnualLeaveId(id);
        log.setEmployeeNo(annualLeave.getEmployeeNo());
        log.setUserId(annualLeave.getUserId());
        log.setAdjustType(adjustType);
        log.setAdjustDays(adjustDays);
        log.setBeforeDays(beforeDays);
        log.setAfterDays(afterDays);
        log.setAdjustReason(adjustReason);
        log.setCreateTime(LocalDateTime.now());

        try {
            Long operatorId = LoginHelper.getUserId();
            String operatorName = LoginHelper.getUsername();
            log.setOperatorId(operatorId);
            log.setOperatorName(operatorName);
        } catch (Exception e) {
            // 如果获取当前用户失败，使用系统用户
            log.setOperatorId(1L);
            log.setOperatorName("系统");
        }

        adjustLogMapper.insert(log);

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deductAnnualLeave(String employeeNo, Integer year, BigDecimal days) {
        // 1. 查询年假记录
        AnnualLeave annualLeave = baseMapper.selectOne(
                Wrappers.lambdaQuery(AnnualLeave.class)
                        .eq(AnnualLeave::getEmployeeNo, employeeNo)
                        .eq(AnnualLeave::getYear, year)
                        .last("LIMIT 1"));

        if (annualLeave == null) {
            throw new ServiceException(String.format("员工%s的%d年年假记录不存在", employeeNo, year));
        }

        // 2. 检查剩余天数是否足够
        if (annualLeave.getRemainingDays().compareTo(days) < 0) {
            throw new ServiceException("剩余年假天数不足");
        }

        // 3. 扣减年假
        BigDecimal beforeDays = annualLeave.getRemainingDays();
        annualLeave.setUsedDays(annualLeave.getUsedDays().add(days));
        annualLeave.setRemainingDays(annualLeave.getRemainingDays().subtract(days));
        baseMapper.updateById(annualLeave);

        // 4. 记录调整日志
        AnnualLeaveAdjustLog log = new AnnualLeaveAdjustLog();
        log.setAnnualLeaveId(annualLeave.getId());
        log.setEmployeeNo(employeeNo);
        log.setUserId(annualLeave.getUserId());
        log.setAdjustType("REDUCE");
        log.setAdjustDays(days);
        log.setBeforeDays(beforeDays);
        log.setAfterDays(annualLeave.getRemainingDays());
        log.setAdjustReason("请假扣减");
        log.setOperatorId(1L);
        log.setOperatorName("系统");
        adjustLogMapper.insert(log);

        return true;
    }

    @Override
    public List<AnnualLeaveAdjustLogVo> queryAdjustLogs(Long annualLeaveId) {
        List<AnnualLeaveAdjustLogVo> logs = adjustLogMapper.selectVoList(
                Wrappers.lambdaQuery(AnnualLeaveAdjustLog.class)
                        .eq(AnnualLeaveAdjustLog::getAnnualLeaveId, annualLeaveId)
                        .orderByDesc(AnnualLeaveAdjustLog::getCreateTime));

        // 填充员工姓名和类型名称
        logs.forEach(log -> {
            if (StringUtils.isNotBlank(log.getEmployeeNo())) {
                HrEmployee employee = employeeMapper.selectOne(
                        Wrappers.lambdaQuery(HrEmployee.class)
                                .eq(HrEmployee::getEmployeeNo, log.getEmployeeNo())
                                .last("LIMIT 1"));
                if (employee != null) {
                    log.setEmployeeName(employee.getEmployeeName());
                }
            }

            // 设置调整类型名称
            if ("ADD".equals(log.getAdjustType())) {
                log.setAdjustTypeName("增加");
            } else if ("REDUCE".equals(log.getAdjustType())) {
                log.setAdjustTypeName("减少");
            }
        });

        return logs;
    }

    /**
     * Date转LocalDate
     */
    private LocalDate convertToLocalDate(Date date) {
        return Instant.ofEpochMilli(date.getTime())
                .atZone(ZoneId.systemDefault())
                .toLocalDate();
    }
}
